package client //Package client import "xemo.top/docker/docker/client"
import (
	"context"
	"github.com/docker/docker/api"
	"net"
	"net/http"
	"net/url"
	"path"
	"strings"
	"xemo.top/docker/docker/api/types"
	"xemo.top/docker/docker/api/types/versions"
)

// Client 是对docker服务器执行所有操作的API客户端
// Client is the API client that performs all operations against a docker server
type Client struct {
	// 方案为客户端设置方案
	// scheme sets the scheme for the client
	scheme string
	// 主机保存要连接到的服务器地址
	// host holds the server address to connect to
	host string
	// 原型保留了客户端协议,即Unix
	// proto holds the client protocol i.e. unix
	proto string
	// addr 保存客户端地址。
	// addr holds the client address.
	addr string
	// basePath 保存请求的前置路径。
	// basePath holds the path to prepend to the requests.
	basePath string
	// client 用于发送和接收http请求
	// client used to send and receive http requests
	client *http.Client
	// version 要与之交谈的服务器的
	// version of the server to talk to
	version string
	// custom 用户配置的http标头
	// custom http headers configured by users
	customHTTPHeaders map[string]string
	// manualOverride 当版本由用户设置时设置为true
	// manualOverride is set to true when the version was set by users
	manualOverride bool
	// negotiateVersion 指示客户端在发出请求时是否应该自动协商要使用的API版本。在第一个请求上执行API版本协商，之后协商设置为 “true”，以便后续请求不会重新协商。
	// negotiateVersion indicates if the client should automatically negotiate the API version to use when making requests.API version negotiation is performed on the first request,after which negotiated is set to "true" so that subsequent requests do not re-negotiate.
	negotiateVersion bool
	// negotiated indicates that API version negotiation took place
	negotiated bool
}

// getAPIPath 返回版本控制的请求路径以调用api。如果查询参数不为空，它会将查询参数附加到路径。
// getAPIPath returns the versioned request path to call the api.It appends the query parameters to the path if they are not empty.
func (cli *Client) getAPIPath(ctx context.Context, p string, query url.Values) string {
	var apiPath string
	if cli.negotiateVersion && !cli.negotiated {
		cli.NegotiateAPIVersion(ctx)
	}
	if cli.version != "" {
		v := strings.TrimPrefix(cli.version, "v")
		apiPath = path.Join(cli.basePath, "/v"+v, p)
	} else {
		apiPath = path.Join(cli.basePath, p)
	}
	return (&url.URL{Path: apiPath, RawQuery: query.Encode()}).String()
}

// NegotiateAPIVersion 查询API并更新版本以匹配API版本。任何错误都将被忽略。如果通过 'DOCKER_API_VERSION' 环境变量进行了手动覆盖，或者如果客户端使用固定版本 ('opts.WithVersion(xx') 进行了初始化，则不会执行任何协商
// NegotiateAPIVersion queries the API and updates the version to match the API version.Any errors are silently ignored.If a manual override is in place,either through the `DOCKER_API_VERSION` environment variable,or if the client was initialized with a fixed version (`opts.WithVersion(xx`),no negotiation will be performed
func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
	if !cli.manualOverride {
		ping, _ := cli.Ping(ctx)
		cli.negotiateAPIVersionPing(ping)
	}
}

// negotiateAPIVersionPing 查询API并更新版本以匹配API版本。任何错误都将被静默忽略。
// negotiateAPIVersionPing queries the API and updates the version to match the API version.Any errors are silently ignored.
func (cli *Client) negotiateAPIVersionPing(p types.Ping) {
	// try the latest version before versioning headers existed
	if p.APIVersion == "" {
		p.APIVersion = "1.24"
	}
	// 如果客户端未使用某个版本进行初始化，请从支持的最新版本开始
	// if the client  is not initialized with a version,start with the latest supported version
	if cli.version == "" {
		cli.version = api.DefaultVersion
	}
	// if server version is lower than the client version,downgrade
	if versions.LessThan(p.APIVersion, cli.version) {
		cli.version = p.APIVersion
	}
	// 存储结果，以便在下一次请求时不会执行自动API版本协商 (如果启用)
	// Store the results,so that automatic API version negotiation (if enabled) won't be performed on the next request
	if cli.negotiateVersion {
		cli.negotiated = true
	}
}

// Dialer 返回原始流连接的拨号程序，带有HTTP/1.1标头，可用于代理守护程序连接。
// Dialer returns a dialer for a raw stream connection,with HTTP/1.1 header,that can be used for proxying the daemon connection.
// Used by `docker  dial-stdio`
func (cli *Client) Dialer() func(context.Context) (net.Conn, error) {
	return func(ctx context.Context) (net.Conn, error) {
		if transport, ok := cli.client.Transport.(*http.Transport); ok {
			if transport.DialContext != nil && transport.TLSClientConfig == nil {
				return transport.DialContext(ctx, cli.proto, cli.addr)
			}
		}
		return fallbackDial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport))
	}
}

// ClientVersion 返回此客户端使用的API版本
// ClientVersion returns the API version used by this client
func (cli *Client) ClientVersion() string {
	return cli.version
}
